/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

// ST1633i - Capacitive Touch Screen Controller

/* includes (local) ----------------------------------------------------------*/
#include <pony/tp/st1633.h>
#include <hal_i2c.h>
#include <hal_gpio.h>
#include <hal_it.h>
/* includes (standard library, system) ---------------------------------------*/
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <x_config.h>
#include <x_module.h>
#include <k_task.h>
#include <k_sem.h>

/* defines -------------------------------------------------------------------*/

/* from STM32U5x9J_DISCOVERY_TS */

/* TS instances */
#define TS_TOUCH_NBR 10U

/* TS orientations */
#define TS_ORIENTATION_PORTRAIT         0U
#define TS_ORIENTATION_LANDSCAPE        1U
#define TS_ORIENTATION_PORTRAIT_ROT180  2U
#define TS_ORIENTATION_LANDSCAPE_ROT180 3U

/* Values Pn_XH and Pn_YH related */
#define SITRONIX_TOUCH_EVT_FLAG_PRESS_DOWN 0x20U
#define SITRONIX_TOUCH_EVT_FLAG_LIFT_UP    0x60U
#define SITRONIX_TOUCH_EVT_FLAG_CONTACT    0x80U
#define SITRONIX_TOUCH_EVT_FLAG_NO_EVENT   0x00U
#define SITRONIX_TOUCH_POS_MSB_MASK        0x07U
#define SITRONIX_TOUCH_POS_LSB_MASK        0x70U

/* Point 1 registers */
#define SITRONIX_P1_XH_REG 0x09U
#define SITRONIX_P1_XL_REG 0x0AU
#define SITRONIX_P1_YH_REG 0x0BU
#define SITRONIX_P1_YL_REG 0x0CU

/* typedefs ------------------------------------------------------------------*/

typedef struct {
    uint32_t Width;
    uint32_t Height;
    uint32_t Orientation;
    uint32_t Accuracy;
    uint32_t MaxX;
    uint32_t MaxY;
    uint32_t PreviousX[TS_TOUCH_NBR];
    uint32_t PreviousY[TS_TOUCH_NBR];
} ts_ctx_t;

typedef struct {
    uint32_t TouchDetected;
    uint32_t TouchX;
    uint32_t TouchY;
} ts_state_t;

typedef struct st1633_dev {
    hal_i2c_dev_t i2c;
    hal_gpio_t it_gpio;
    task_id task;
    sem_id sem;
    ts_ctx_t ctx;
    ts_state_t state;
} st1633_dev_t;

/* macro ---------------------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/

static st1633_dev_t s_st1633;

/* variables (global) --------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

static int __st1633_get_state(st1633_dev_t *dev, ts_state_t *state)
{
    uint8_t data[5] = {0};
    if (hal_i2c_dev_recv(&dev->i2c, data, (uint16_t)sizeof(data)) != 0) {
        return -1;
    }
    ts_state_t raw_state;
    /* Send back first ready X position to caller */
    raw_state.TouchX =
        ((((uint32_t)data[2] & SITRONIX_TOUCH_POS_LSB_MASK) << 4) | ((uint32_t)data[3]));
    /* Send back first ready Y position to caller */
    raw_state.TouchY =
        (((uint32_t)data[2] & SITRONIX_TOUCH_POS_MSB_MASK) << 8) | ((uint32_t)data[4]);

    if ((data[2] & 0x80U) == 0x80U) {
        raw_state.TouchDetected = 1;
    } else {
        raw_state.TouchDetected = 0;
    }

    ts_ctx_t *ctx = &dev->ctx;
    /* Check and update the number of touches active detected */
    if (raw_state.TouchDetected != 0U) {
        uint32_t x_oriented;
        uint32_t y_oriented;
        uint32_t x_diff;
        uint32_t y_diff;

        x_oriented = /*ctx->MaxX -*/ raw_state.TouchX;
        y_oriented = /*ctx->MaxY -*/ raw_state.TouchY;

        /* Apply boundary */
        state->TouchX = (x_oriented * ctx->Width) / (ctx->MaxX);
        state->TouchY = (y_oriented * ctx->Height) / (ctx->MaxY);
        /* Store Current TS raw_state */
        state->TouchDetected = raw_state.TouchDetected;

        /* Check accuracy */
        x_diff = (state->TouchX > ctx->PreviousX[0]) ? (state->TouchX - ctx->PreviousX[0])
                                                     : (ctx->PreviousX[0] - state->TouchX);

        y_diff = (state->TouchY > ctx->PreviousY[0]) ? (state->TouchY - ctx->PreviousY[0])
                                                     : (ctx->PreviousY[0] - state->TouchY);

        if ((x_diff > ctx->Accuracy) || (y_diff > ctx->Accuracy)) {
            /* New touch detected */
            ctx->PreviousX[0] = state->TouchX;
            ctx->PreviousY[0] = state->TouchY;
        } else {
            state->TouchX = ctx->PreviousX[0];
            state->TouchY = ctx->PreviousY[0];
        }
    } else {
        state->TouchDetected = 0U;
        state->TouchX = ctx->PreviousX[0];
        state->TouchY = ctx->PreviousY[0];
    }

    return 0;
}

static void __st1633_it_handler(uintptr_t arg)
{
    st1633_dev_t *dev = (st1633_dev_t *)arg;
    hal_gpio_it_handler(&dev->it_gpio);
    sem_post(dev->sem);
}

static int __st1633_task(uintptr_t arg)
{
    int ret;
    st1633_dev_t *dev = (st1633_dev_t *)arg;

    dev->sem = sem_create(0);
    if (dev->sem == NULL) {
        return -1;
    }

    /* enable interrupt */
    hal_gpio_irq(&dev->it_gpio, 1);

    for (;;) {
        ret = sem_wait(dev->sem);
        if (ret != 0) {
            continue;
        }
        __st1633_get_state(dev, &dev->state);
    }
    return 0;
}

static int __st1633_gpio_init(st1633_dev_t *dev)
{
    hal_gpio_t *io = NULL;

    /* interrupt */
    io = &dev->it_gpio;
    io->gpio = CONFIG_TP_ST1633_INT_PORT;
    io->pin = CONFIG_TP_ST1633_INT_PIN;
    io->mode = HAL_GPIO_MODE_IT_FALLING;
    io->pull = HAL_GPIO_PULLUP;
    io->handler = __st1633_it_handler;
    io->prio = 0x0F;
    io->arg = (uintptr_t)dev;
    if (hal_gpio_init(io) != 0) {
        return -1;
    }

    return 0;
}

static void __stm1633_config(st1633_dev_t *dev)
{
    uint32_t i;
    ts_ctx_t *ctx = &dev->ctx;

    /* Store parameters on TS context */
    ctx->Width = CONFIG_LCD_XRES;
    ctx->Height = CONFIG_LCD_YRES;
    ctx->Orientation = TS_ORIENTATION_PORTRAIT;
    ctx->Accuracy = 0;

    /* Store maximum X and Y on context */
    ctx->MaxX = CONFIG_LCD_XRES;
    ctx->MaxY = CONFIG_LCD_YRES;

    /* Initialize previous position in order to always detect first touch */
    for (i = 0; i < TS_TOUCH_NBR; i++) {
        ctx->PreviousX[i] = ctx->Width + ctx->Accuracy + 1U;
        ctx->PreviousY[i] = ctx->Height + ctx->Accuracy + 1U;
    }
}

static int __st1633_init(struct hal_i2c_dev *i2c_dev)
{
    st1633_dev_t *dev = container_of(i2c_dev, st1633_dev_t, i2c);

    dev->task = task_spawn("tp_task", 15, 0, 0x80, __st1633_task, (uintptr_t)dev);
    if (dev->task == NULL) {
        return -1;
    }
    return 0;
}

int tp_get_point(touch_point_t *point)
{
    ts_state_t *state = &s_st1633.state;

    point->state = state->TouchDetected;
    point->x = state->TouchX;
    point->y = state->TouchY;

    return 0;
}

static int __st1633_open(hal_dev_t *dev, int oflag, va_list valist)
{
    st1633_dev_t *d = (st1633_dev_t *)dev;

    if (__st1633_gpio_init(d) != 0) {
        return -1;
    }
    __stm1633_config(d);

    return __st1633_init(&d->i2c);
}

static hal_dev_ops_t dev_ops = {
    .open = __st1633_open,
};

static int st1633_init(void)
{
    if (hal_i2c_dev_register(&s_st1633.i2c, CONFIG_TP_ST1633_I2C_BUS, "st1633",
                             CONFIG_TP_ST1633_I2C_ADDR) != 0) {
        return -1;
    }
    if (hal_dev_init((hal_dev_t *)&s_st1633, &dev_ops) != 0) {
        return -2;
    }

    return 0;
}

MODULE_INIT(driver, st1633_init);
